package com.symagic.gesture;

import java.util.ArrayList;

import javax.microedition.khronos.opengles.GL10;

import android.gesture.GesturePoint;
import android.gesture.Prediction;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.VertexAttribute;
import com.badlogic.gdx.graphics.VertexAttributes.Usage;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.scenes.scene2d.Action;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.OnActionCompleted;

public class SymagicGestureView extends SymagicActor {

	// This is a data structure which is used to recode the gesture recognize
	// history
	public class SymagicGestureViewHistory {
		private String name;
		private double score;
		private ArrayList<GesturePoint> points;

		// Construct method, the all in one builder
		public SymagicGestureViewHistory(String name, double score,
				ArrayList<GesturePoint> points) {
			this.name = name;
			this.score = score;
			this.points = points;
		}

		// the getter and setter
		public String getName() {
			return name;
		}

		public void setName(String name) {
			this.name = name;
		}

		public double getScore() {
			return score;
		}

		public void setScore(double score) {
			this.score = score;
		}

		public ArrayList<GesturePoint> getGesturePoint() {
			return points;
		}

		public void setGesturePoint(ArrayList<GesturePoint> points) {
			this.points = points;
		}

	}

	// the backgroud texture
	private Texture background = null;

	// the scale float
	private float xScaleRatio;
	private float yScaleRatio;

	// the device's height and width logger
	private float deviceHeight;
	private float deviceWidth;

	// the gesture line attribute
	private float lineWeight;
	private float lineColorRed, lineColorGreen, lineColorBlue, lineColorAlpha;

	// prepare for the animation
	private float animationBaseHeight;

	// animation processing flag
	private boolean beingAnimation;
	private float animationDuringTime;

	// the background borad mesh
	private Mesh backgroundMesh = null;

	// the gesture points buffer;
	ArrayList<GesturePoint> gesturePoints;

	// the gesture recognize history logger
	ArrayList<SymagicGestureViewHistory> history;

	// the shader program for opengl es 2.0
	ShaderProgram program = null;

	// the observer list
	ArrayList<SymagicGesturePerformedObserver> observerList;

	public SymagicGestureView(SymagicGestureLogic gestureLogic, float alpha) {
		super(gestureLogic);
		// TODO Auto-generated constructor stub
		init(alpha);
	}

	public SymagicGestureView(String name, SymagicGestureLogic gestureLogic, float alpha) {
		super(name, gestureLogic);
		// TODO Auto-generated constructor stub
		init(alpha);
	}

	// the init function handle the common initiation of the class
	private void init( float alpha ) {

		// read the texture from file
		background = new Texture(Gdx.files.internal("drawPaneBG.png"));

		// calculate the scale ratio
		xScaleRatio = Gdx.graphics.getWidth() / 480.0f;
		yScaleRatio = Gdx.graphics.getHeight() / 800.0f;

		// setting the position
		this.x = 5 * xScaleRatio;
		this.y = 100 * yScaleRatio;
		this.width = 340 * xScaleRatio;
		this.height = 550 * yScaleRatio;

		this.touchable = true;

		// set the gesture performed callback
		// the function is called when a gesture is performed.
		this.setCallback(new SymagicGesturePerformedListener() {

			@Override
			public void onGesturePerformed(Prediction prediction,
					ArrayList<GesturePoint> points) {
				// TODO Auto-generated method stub
				gesturePoints = points;

				history.add(new SymagicGestureViewHistory(prediction.name,
						prediction.score, points));

				// Output for debug
				System.out.println(prediction.name + " : " + prediction.score);
				// debug output end

				// setup the action
				SymagicGestureMoveToAction action = SymagicGestureMoveToAction
						.$(0, 0, animationDuringTime);

				action.setCompletionListener(new OnActionCompleted() {

					public void completed(Action arg0) {
						// TODO Auto-generated method stub
						beingAnimation = false;
						animationBaseHeight = 0;
						gesturePoints = null;
					}

				});

				SymagicGestureView.this.action(action);
				beingAnimation = true;

				// Notify the observer
				for (int i = 0; i < observerList.size(); i++) {
					observerList.get(i).update(SymagicGestureView.this);
				}

			}
		});

		deviceHeight = Gdx.graphics.getHeight();
		deviceWidth = Gdx.graphics.getWidth();

		lineWeight = 1;
		lineColorRed = 1.0f;
		lineColorGreen = 0;
		lineColorBlue = 0;
		lineColorAlpha = 1.0f * alpha;

		// init the history
		history = new ArrayList<SymagicGestureViewHistory>();

		// init the animation base height
		animationBaseHeight = 0;

		// init the animation processing flag
		beingAnimation = false;

		// init the animation during time
		animationDuringTime = 2;

		// init the observer list
		observerList = new ArrayList<SymagicGesturePerformedObserver>();

		// init the mesh vertex list
		backgroundMesh = new Mesh(true, 4, 4, new VertexAttribute(
				Usage.Position, 3, "backgroundPosition"), new VertexAttribute(
				Usage.ColorPacked, 4, "backgroundColor"), new VertexAttribute(
				Usage.TextureCoordinates, 2, "backgroundTexCoords"));

		backgroundMesh.setVertices(new float[] { 2 * this.x / deviceWidth - 1,
				2 * (this.y) / deviceHeight - 1, 0,
				Color.toFloatBits(1.0f, 1.0f, 1.0f, alpha), 1, 1,
				2 * (this.x + this.width) / deviceWidth - 1,
				2 * (this.y) / deviceHeight - 1, 0,
				Color.toFloatBits(1.0f, 1.0f, 1.0f, alpha), 0, 1,
				2 * (this.x + this.width) / deviceWidth - 1,
				2 * (this.y + this.height) / deviceHeight - 1,
				0, Color.toFloatBits(1.0f, 1.0f, 1.0f, alpha), 0, 0,
				2 * this.x / deviceWidth - 1,
				2 * (this.y + this.height) / deviceHeight - 1,
				0, Color.toFloatBits(1.0f, 1.0f, 1.0f, alpha), 1, 0, });

		backgroundMesh.setIndices(new short[] { 0, 1, 2, 3, });

		// when the device is opengl es 2.0, we init the shader program
		/*
		 * if( Gdx.graphics.isGL20Available() ) program = new ShaderProgram(
		 * Gdx.files.internal("gestureVertex.glsl").readString(),
		 * Gdx.files.internal("gestureFragment.glsl").readString() );
		 */

	}

	@Override
	public Actor hit(float arg0, float arg1) {
		// TODO Auto-generated method stub
		// when being animation, user can't draw the gesture
		if (beingAnimation)
			return null;

		return super.hit(arg0, arg1);
	}

	// the animation during time setter
	public void setAnimationDuringTime(float time) {
		animationDuringTime = time;
	}

	// the animation base height getter and setter
	public float getAnimationBaseHeight() {
		return animationBaseHeight;
	}

	public void setAnimationBaseHeight(float animationBaseHeight) {
		this.animationBaseHeight = animationBaseHeight;
	}

	// set the line width for the gesture's line
	public void setLineWeight(float weight) {
		lineWeight = weight;
	}

	// set the line color for the gesture's line
	public void setLineColor(short red, short green, short blue, short alpha) {
		lineColorRed = red;
		lineColorGreen = green;
		lineColorBlue = blue;
		lineColorAlpha = alpha;
	}

	public ArrayList<SymagicGestureViewHistory> getRecognizeHistory() {
		return history;
	}

	public void clearRecognizeHistory() {
		history.clear();
	}

	/*
	 * override the SymagicActor's touchDown function the SymagicActor's see
	 * com.symagic.gesture.SymagicActor::touchDown(float, float, int)
	 */
	@Override
	public boolean touchDown(float arg0, float arg1, int arg2) {
		// TODO Auto-generated method stub
		gesturePoints = null;
		return super.touchDown(arg0, arg1, arg2);
	}

	/*
	 * override the SymagicActor's touchDragged function if the event is
	 * happened in the view we pass to the SymagicActor, else we discards it.
	 * see com.symagic.gesture.SymagicActor::touchDragged(float, float, int)
	 */
	@Override
	public void touchDragged(float arg0, float arg1, int arg2) {
		// TODO Auto-generated method stub
		if (arg0 >= 0 && arg0 <= this.width && arg1 >= 0 && arg1 <= this.height) {
			// the event is happened in the view
			super.touchDragged(arg0 + this.x, deviceHeight - arg1 - this.y,
					arg2);
		}
	}

	/*
	 * override the SymagicActor's draw function the SymagicActor's draw
	 * function is just draw the gesture point but now we need to draw the
	 * background of the view, because of the engine, the gesture point drawing
	 * can't place here, it will draw nothing. see
	 * com.symagic.gesture.SymagicActor
	 * #draw(com.badlogic.gdx.graphics.g2d.SpriteBatch, float)
	 */
	@Override
	public void draw(SpriteBatch arg0, float arg1) {
		// TODO Auto-generated method stub
		arg0.end();

		drawGesture();

		arg0.begin();

		// super.draw(arg0, arg1);
	}

	// the gesture logic getter
	public SymagicGestureLogic getGestureLogic() {
		return gestureLogic;
	}

	// add the listener
	public void addObserver(SymagicGesturePerformedObserver observer) {
		observerList.add(observer);
	}

	// use opengl to draw gesture
	private void drawGesture() {

		ArrayList<GesturePoint> points = gesturePoints;

		if (points == null)
			points = gestureLogic.getCurrentGesturePoints();
		glCommonDrawGesture(points);
	}

	// use the common opengl interface to draw the gesture points, this way of
	// drawing can work in any device.
	private void glCommonDrawGesture(ArrayList<GesturePoint> points) {
		
		//save matrix
		Gdx.gl10.glMatrixMode(GL10.GL_MODELVIEW);
		Gdx.gl10.glPushMatrix();
		Gdx.gl10.glMatrixMode(GL10.GL_PROJECTION);
		Gdx.gl10.glPushMatrix();
		
		// set the weight
		Gdx.gl.glLineWidth(lineWeight);

		Gdx.gl10.glMatrixMode(GL10.GL_MODELVIEW);
		Gdx.gl10.glLoadIdentity();
		Gdx.gl10.glMatrixMode(GL10.GL_PROJECTION);
		Gdx.gl10.glLoadIdentity();
		Gdx.gl10.glMatrixMode(GL10.GL_MODELVIEW);
		

		// set the smooth for point and line
		Gdx.gl.glEnable(GL10.GL_POINT_SMOOTH);
		Gdx.gl.glEnable(GL10.GL_LINE_SMOOTH);
		Gdx.gl.glHint(GL10.GL_POINT_SMOOTH_HINT, GL10.GL_NICEST);
		Gdx.gl.glHint(GL10.GL_LINE_SMOOTH, GL10.GL_NICEST);

		Gdx.gl.glEnable(GL10.GL_SCISSOR_TEST);
		Gdx.gl.glScissor((int) this.x, (int) this.y, (int) this.width,
				(int) this.height);

		Gdx.gl.glEnable(GL10.GL_TEXTURE_2D);
		Gdx.gl.glEnable(GL10.GL_BLEND);
		Gdx.gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);

		// draw the background

		background.bind();
		backgroundMesh.render(GL10.GL_TRIANGLE_FAN);

		Gdx.gl10.glBindTexture(GL10.GL_TEXTURE_2D, 0);

		if (points != null && points.size() > 0) {
			// the vertex buffer and the index buffer
			float[] vertex = new float[points.size() * 8];
			short[] index = new short[points.size() << 1];
			/*
			 * build the texture buffer engine's vertex array must be ( x, y, z,
			 * color ) the GL_LINES style get two vertex in the buffer to draw a
			 * line.
			 */
			for (short i = 0; i < points.size() - 1; i++) {
				// use ( i << 3 ) instead of i * 8 for high speed
				// use (((i << 1 ) + 1 ) << 2 ) instead of ( ( i * 2 ) + 1 ) * 4
				// for
				// high speed
				vertex[(i << 3)] = 2 * points.get(i).x / deviceWidth - 1;
				vertex[(i << 3) + 1] = 2
						* (deviceHeight - points.get(i).y + animationBaseHeight)
						/ deviceHeight - 1;
				vertex[(i << 3) + 2] = 0;
				vertex[(i << 3) + 3] = Color.toFloatBits(lineColorRed,
						lineColorGreen, lineColorBlue, lineColorAlpha);

				vertex[(((i << 1) + 1) << 2)] = 2 * points.get(i + 1).x
						/ deviceWidth - 1;
				vertex[(((i << 1) + 1) << 2) + 1] = 2
						* (deviceHeight - points.get(i + 1).y + animationBaseHeight)
						/ deviceHeight - 1;
				vertex[(((i << 1) + 1) << 2) + 2] = 0;
				vertex[(((i << 1) + 1) << 2) + 3] = Color.toFloatBits(
						lineColorRed, lineColorGreen, lineColorBlue,
						lineColorAlpha);
			}

			for (short i = 0; i < points.size() << 1; i++) {
				index[i] = i;
			}

			// rend the mesh, the rending only work in opengl es 1.0 and opengl
			// es
			// 1.1
			// when it works in opengl es 2.0, it will throw a exception

			// setup the gesture mesh
			Mesh gestureMesh = new Mesh(true, points.size() << 1,
					points.size() << 1, new VertexAttribute(Usage.Position, 3,
							"position"), new VertexAttribute(Usage.ColorPacked,
							4, "color"));
			gestureMesh.setVertices(vertex);
			gestureMesh.setIndices(index);

			gestureMesh.render(GL10.GL_LINES, 0, (points.size() - 1) << 1);
			gestureMesh.dispose();

		}

		Gdx.gl.glDisable(GL10.GL_SCISSOR_TEST);
		Gdx.gl.glDisable(GL10.GL_TEXTURE_2D);
		Gdx.gl.glDisable(GL10.GL_BLEND);
		
		Gdx.gl10.glMatrixMode(GL10.GL_MODELVIEW);
		Gdx.gl10.glPopMatrix();
		Gdx.gl10.glMatrixMode(GL10.GL_PROJECTION);
		Gdx.gl10.glPopMatrix();
		
	}

}
